#region Namespaces

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

#endregion

namespace Vibstudio.X4NET.Xml.Linq
{
    public class XAttribute : XObject
    {
        private static readonly XAttribute[] emptyArray = new XAttribute[0];

        private static readonly char[] escapeChars = new char[] { '<', '>', '&', '"', '\r', '\n', '\t' };

        private readonly XName _name;

        private string _value;

        public XAttribute(XAttribute other)
        {
            if (other == null)
            {
                throw new ArgumentNullException("other");
            }
            _name = other._name;
            _value = other._value;
        }

        public XAttribute(XName name, object value)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }
            _name = name;
            SetValue(value);
        }

        public static IEnumerable<XAttribute> EmptySequence
        {
            get { return emptyArray; }
        }

        public bool IsNamespaceDeclaration
        {
            get
            {
                return _name.Namespace == XNamespace.Xmlns ||
                       (_name.LocalName == "xmlns" && _name.Namespace == XNamespace.None);
            }
        }

        public XName Name
        {
            get { return _name; }
        }

        public XAttribute NextAttribute { get; internal set; }

        public override XmlNodeType NodeType
        {
            get { return XmlNodeType.Attribute; }
        }

        public XAttribute PreviousAttribute { get; internal set; }

        public string Value
        {
            get { return XUtil.ToString(_value); }
            set { SetValue(value); }
        }

        [CLSCompliant(false)]
        public static explicit operator bool(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XUtil.ConvertToBoolean(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator bool?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (bool?)null : XUtil.ConvertToBoolean(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator DateTime(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XUtil.ToDateTime(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator DateTime?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (DateTime?)null : XUtil.ToDateTime(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator DateTimeOffset(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToDateTimeOffset(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator DateTimeOffset?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (DateTimeOffset?)null : XmlConvert.ToDateTimeOffset(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator decimal(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToDecimal(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator decimal?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (decimal?)null : XmlConvert.ToDecimal(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator double(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToDouble(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator double?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (double?)null : XmlConvert.ToDouble(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator float(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToSingle(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator float?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (float?)null : XmlConvert.ToSingle(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator Guid(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToGuid(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator Guid?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (Guid?)null : XmlConvert.ToGuid(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator int(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToInt32(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator int?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (int?)null : XmlConvert.ToInt32(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator long(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToInt64(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator long?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (long?)null : XmlConvert.ToInt64(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator uint(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToUInt32(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator uint?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (uint?)null : XmlConvert.ToUInt32(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator ulong(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToUInt64(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator ulong?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (ulong?)null : XmlConvert.ToUInt64(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator TimeSpan(XAttribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            return XmlConvert.ToTimeSpan(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator TimeSpan?(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value == null ? (TimeSpan?)null : XmlConvert.ToTimeSpan(attribute._value);
        }

        [CLSCompliant(false)]
        public static explicit operator string(XAttribute attribute)
        {
            if (attribute == null)
            {
                return null;
            }

            return attribute._value;
        }

        public void Remove()
        {
            if (Parent != null)
            {
                var owner = Owner;
                owner.OnRemovingObject(this);
                if (NextAttribute != null)
                {
                    NextAttribute.PreviousAttribute = PreviousAttribute;
                }
                if (PreviousAttribute != null)
                {
                    PreviousAttribute.NextAttribute = NextAttribute;
                }
                if (Parent.FirstAttribute == this)
                {
                    Parent.FirstAttribute = NextAttribute;
                }
                if (Parent.LastAttribute == this)
                {
                    Parent.LastAttribute = PreviousAttribute;
                }
                SetOwner(null);
                owner.OnRemovedObject(this);
            }
            NextAttribute = null;
            PreviousAttribute = null;
        }

        public void SetValue(object value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            OnValueChanging(this);
            this._value = XUtil.ToString(value);
            OnValueChanged(this);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(_name.ToString());
            sb.Append("=\"");
            int start = 0;
            do
            {
                int idx = _value.IndexOfAny(escapeChars, start);
                if (idx < 0)
                {
                    if (start > 0)
                    {
                        sb.Append(_value, start, _value.Length - start);
                    }
                    else
                    {
                        sb.Append(_value);
                    }
                    sb.Append("\"");
                    return sb.ToString();
                }
                sb.Append(_value, start, idx - start);
                switch (_value[idx])
                {
                    case '&':
                        sb.Append("&amp;");
                        break;
                    case '<':
                        sb.Append("&lt;");
                        break;
                    case '>':
                        sb.Append("&gt;");
                        break;
                    case '"':
                        sb.Append("&quot;");
                        break;
                    case '\r':
                        sb.Append("&#xD;");
                        break;
                    case '\n':
                        sb.Append("&#xA;");
                        break;
                    case '\t':
                        sb.Append("&#x9;");
                        break;
                }
                start = idx + 1;
            }
            while (true);
        }
    }
}